// 070802;080404 alexch
//
// ==========  This file is under  LGPL, the GNU Lesser General Public Licence
// ==========  Dialing Lemmatizer (www.aot.ru)
// ==========  Copyright by Alexey Sokirko 
//
// ==========  Visual Studio project for 32/64 bit DLL-wrapper
// ==========  Created by Hulbee AG

#pragma warning(disable: 4786)
#include "StlWinF.h"
#include "StlSysF.h"
#include "guards.h"

#include <malloc.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <assert.h>

namespace StlFunc {

bool StrA2W(const str_t &src, wstr_t &dst)
{
	if (src.empty()) {
		dst.erase();
		return true;
	}
	wchar_t *wbuf;
	size_t len = src.size();
	assert(len <= INT_MAX);
	size_t sz = (len + 1) * sizeof(wbuf[0]);
	MemoryGuard mg;
	if (sz <= MaxStackAlloc)
		wbuf = (wchar_t*)_alloca(sz);
	else {
		wbuf = (wchar_t*)malloc(sz);
		if (!wbuf)
			return false;
		mg.SetMem(wbuf);
	}
	int n = MultiByteToWideChar(CP_ACP, 0, src.data(), (int)len, wbuf, (int)len);
	if (n <= 0)
		return false;
	wbuf[n] = 0;
	dst = wbuf;
	return true;
}

bool StrA2W(const char *src, wstr_t &dst)
{
	if (!src) {
		dst.erase();
		return true;
	}
	str_t s = src;
	return StrA2W(s, dst);
}

bool StrW2A(const wstr_t &src, str_t &dst, LPCSTR DefaultChar, LPBOOL UsedDefaultChar)
{
	if (src.empty()) {
		dst.erase();
		if (UsedDefaultChar)
			*UsedDefaultChar = FALSE;
		return true;
	}
	char *abuf;
	size_t len = src.size();
	size_t sz = len * 2; // for DBCS
	assert(sz <= INT_MAX);
	MemoryGuard mg;
	if (sz <= MaxStackAlloc)
		abuf = (char*)_alloca(sz + 1);
	else {
		abuf = (char*)malloc(sz + 1);
		if (!abuf)
			return false;
		mg.SetMem(abuf);
	}
	int n = WideCharToMultiByte(CP_ACP, 0, src.data(), (int)len, abuf, (int)sz,
		DefaultChar, UsedDefaultChar);
	if (n <= 0)
		return false;
	abuf[n] = 0;
	dst = abuf;
	return true;
}

bool StrW2A(const wchar_t *src, str_t &dst, LPCSTR DefaultChar, LPBOOL UsedDefaultChar)
{
	if (!src) {
		dst.erase();
		if (UsedDefaultChar)
			*UsedDefaultChar = FALSE;
		return true;
	}
	wstr_t s = src;
	return StrW2A(s, dst, DefaultChar, UsedDefaultChar);
}

bool ExactStrW2A(const wstr_t &src, str_t &dst)
{
	BOOL UsedDefChar;
	return StrW2A(src, dst, "?", &UsedDefChar) && !UsedDefChar;
}

bool ExactStrW2A(const wchar_t *src, str_t &dst)
{
	BOOL UsedDefChar;
	return StrW2A(src, dst, "?", &UsedDefChar) && !UsedDefChar;
}

#if _MSC_VER >= 1300

template <class STR_TYPE, DWORD WINAPI FUNC(typename STR_TYPE::value_type*, DWORD)>
	inline static STR_TYPE& TUpperCase(STR_TYPE &s)
{
	size_t len = s.size();
	assert(len <= ULONG_MAX);
	STR_TYPE::value_type *alloced = 0, *p;
	if (len * sizeof(STR_TYPE::value_type) <= MaxStackAlloc)
		p = (STR_TYPE::value_type*)_alloca(len * sizeof(STR_TYPE::value_type));
	else {
		alloced = new STR_TYPE::value_type[len];
		p = alloced;
	}
	memcpy(p, s.data(), len * sizeof(STR_TYPE::value_type));
	s.assign(p, FUNC(p, (ULONG)len));
	delete[] alloced;
	return s;
}

str_t& UpperCase(str_t &s)
{
	return TUpperCase<str_t, CharUpperBuffA>(s);
}

wstr_t& UpperCase(wstr_t &s)
{
	return TUpperCase<wstr_t, CharUpperBuffW>(s);
}

template <class STR_TYPE, DWORD WINAPI FUNC(typename STR_TYPE::value_type*, DWORD)>
	inline static STR_TYPE& TLowerCase(STR_TYPE &s)
{
	size_t len = s.size();
	assert(len <= ULONG_MAX);
	STR_TYPE::value_type *alloced = 0, *p;
	if (len * sizeof(STR_TYPE::value_type) <= MaxStackAlloc)
		p = (STR_TYPE::value_type*)_alloca(len * sizeof(STR_TYPE::value_type));
	else {
		alloced = new STR_TYPE::value_type[len];
		p = alloced;
	}
	memcpy(p, s.data(), len * sizeof(STR_TYPE::value_type));
	s.assign(p, FUNC(p, (ULONG)len));
	delete[] alloced;
	return s;
}

str_t& LowerCase(str_t &s)
{
	return TLowerCase<str_t, CharLowerBuffA>(s);
}

wstr_t& LowerCase(wstr_t &s)
{
	return TLowerCase<wstr_t, CharLowerBuffW>(s);
}

#else

#define UPPER_CASE(STR_TYPE, FUNC) STR_TYPE& UpperCase(STR_TYPE &s) \
{ \
	size_t len = s.size(); \
	STR_TYPE::value_type *alloced = 0, *p; \
	if (len * sizeof(STR_TYPE::value_type) <= MaxStackAlloc) \
		p = (STR_TYPE::value_type*)_alloca(len * sizeof(STR_TYPE::value_type)); \
	else { \
		alloced = new STR_TYPE::value_type[len]; \
		p = alloced; \
	} \
	memcpy(p, s.data(), len * sizeof(STR_TYPE::value_type)); \
	s.assign(p, FUNC(p, len)); \
	delete[] alloced; \
	return s; \
}

UPPER_CASE(str_t, CharUpperBuffA)
UPPER_CASE(wstr_t, CharUpperBuffW)

#define LOWER_CASE(STR_TYPE, FUNC) STR_TYPE& LowerCase(STR_TYPE &s) \
{ \
	size_t len = s.size(); \
	STR_TYPE::value_type *alloced = 0, *p; \
	if (len * sizeof(STR_TYPE::value_type) <= MaxStackAlloc) \
		p = (STR_TYPE::value_type*)_alloca(len * sizeof(STR_TYPE::value_type)); \
	else { \
		alloced = new STR_TYPE::value_type[len]; \
		p = alloced; \
	} \
	memcpy(p, s.data(), len * sizeof(STR_TYPE::value_type)); \
	s.assign(p, FUNC(p, len)); \
	delete[] alloced; \
	return s; \
}

LOWER_CASE(str_t, CharLowerBuffA)
LOWER_CASE(wstr_t, CharLowerBuffW)

#endif

int StrCmp(const str_t &str1, const str_t &str2, unsigned int maxCmpChars)
{
	size_t len1 = str1.size();
	assert(len1 <= INT_MAX);
	if (maxCmpChars && maxCmpChars < len1)
		len1 = maxCmpChars;
	size_t len2 = str2.size();
	assert(len2 <= INT_MAX);
	if (maxCmpChars && maxCmpChars < len2)
		len2 = maxCmpChars;
	return CompareStringA(0, SORT_STRINGSORT, str1.data(), (int)len1, str2.data(), (int)len2) - 2;
}

int StrICmp(const str_t &str1, const str_t &str2, unsigned int maxCmpChars)
{
	size_t len1 = str1.size();
	assert(len1 <= INT_MAX);
	if (maxCmpChars && maxCmpChars < len1)
		len1 = maxCmpChars;
	size_t len2 = str2.size();
	assert(len2 <= INT_MAX);
	if (maxCmpChars && maxCmpChars < len2)
		len2 = maxCmpChars;
	return CompareStringA(0, NORM_IGNORECASE | SORT_STRINGSORT, str1.data(), (int)len1,
		str2.data(), (int)len2) - 2;
}

int WStrCmp(const wstr_t &str1, const wstr_t &str2, unsigned int maxCmpChars)
{
	size_t len1 = str1.size();
	assert(len1 <= INT_MAX);
	if (maxCmpChars && maxCmpChars < len1)
		len1 = maxCmpChars;
	size_t len2 = str2.size();
	assert(len2 <= INT_MAX);
	if (maxCmpChars && maxCmpChars < len2)
		len2 = maxCmpChars;
	return CompareStringW(0, SORT_STRINGSORT, str1.data(), (int)len1,
		str2.data(), (int)len2) - 2;
}

int WStrICmp(const wstr_t &str1, const wstr_t &str2, unsigned int maxCmpChars)
{
	size_t len1 = str1.size();
	if (maxCmpChars && maxCmpChars < len1)
		len1 = maxCmpChars;
	assert(len1 <= INT_MAX);
	size_t len2 = str2.size();
	if (maxCmpChars && maxCmpChars < len2)
		len2 = maxCmpChars;
	assert(len2 <= INT_MAX);
	return CompareStringW(0, NORM_IGNORECASE | SORT_STRINGSORT, str1.data(), (int)len1,
		str2.data(), (int)len2) - 2;
}

bool ExpandEnvVars(str_t &s)
{
	assert(s.size() < INT_MAX);
	DWORD sz = __max((DWORD)s.size() * 2, MAX_PATH);
	char *dst;
	bool b = false;
	while (1) {
		dst = new char[sz];
		DWORD n = ExpandEnvironmentStringsA(s.c_str(), dst, sz);
		if (n <= sz)
			break;
		delete[] dst;
		if (!n || b)
			return false;
		sz = n;
		b = true;
	}
	s = dst;
	delete[] dst;
	return true;
}

bool ExpandEnvVars(wstr_t &s)
{
	assert(s.size() < INT_MAX);
	DWORD sz = __max((DWORD)s.size() * 2, MAX_PATH);
	wchar_t *dst;
	bool b = false;
	while (1) {
		dst = new wchar_t[sz];
		DWORD n = ExpandEnvironmentStringsW(s.c_str(), dst, sz);
		if (n <= sz)
			break;
		delete[] dst;
		if (!n || b)
			return false;
		sz = n;
		b = true;
	}
	s = dst;
	delete[] dst;
	return true;
}

bool GetSystemName(str_t &s)
{
	CHAR buf[__max(512, MAX_COMPUTERNAME_LENGTH + 1)];
	DWORD sz = ARRAY_COUNT(buf);
	if (GetComputerNameA(buf, &sz)) {
		s = buf;
		return true;
	}
	s.erase();
	return false;
}

bool GetSystemName(wstr_t &s)
{
	WCHAR buf[__max(512, MAX_COMPUTERNAME_LENGTH + 1)];
	DWORD sz = ARRAY_COUNT(buf);
	if (GetComputerNameW(buf, &sz)) {
		s = buf;
		return true;
	}
	s.erase();
	return false;
}

#if (_WIN32_WINNT >= 0x0500)

bool GetSystemNameEx(str_t &s, COMPUTER_NAME_FORMAT fmt)
{
	CHAR buf[__max(512, MAX_COMPUTERNAME_LENGTH + 1)];
	DWORD sz = ARRAY_COUNT(buf);
	if (GetComputerNameExA(fmt, buf, &sz)) {
		s = buf;
		return true;
	}
	s.erase();
	return false;
}

bool GetSystemNameEx(wstr_t &s, COMPUTER_NAME_FORMAT fmt)
{
	WCHAR buf[__max(512, MAX_COMPUTERNAME_LENGTH + 1)];
	DWORD sz = ARRAY_COUNT(buf);
	if (GetComputerNameExW(fmt, buf, &sz)) {
		s = buf;
		return true;
	}
	s.erase();
	return false;
}

#endif

// system errors

bool SysErrorMessage(DWORD code, wstr_t &dst)
{
	WCHAR buf[1024];
	DWORD sz = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS |
		FORMAT_MESSAGE_ARGUMENT_ARRAY, 0, code, 0, buf, sizeof(buf) / sizeof(buf[0]), 0);
	while (sz && (buf[sz - 1] <= ' ' || buf[sz - 1] == '.')) --sz;
	dst.assign(buf, sz);
	size_t dblsp = 0;
	while ((dblsp = dst.find(L"  ", dblsp)) != wstr_t::npos) {
		dst.erase(dblsp, 1);
		--sz;
	}
	return sz > 0;
}

wstr_t SysErrorMessage(DWORD code)
{
	wstr_t w;
	if (!SysErrorMessage(code, w))
		StrPrintf(L"Unknown Win32 error %u", code);
	return w;
}

// date & time

bool FileTimeToString(FILETIME& fileTime, wstr_t &strDateTime, bool date, bool time)
{
	strDateTime.erase();
    // compensate for local TZ
    FILETIME ftLocal;
    if (!FileTimeToLocalFileTime(&fileTime, &ftLocal))
		return false;
    // convert it to system time
    SYSTEMTIME stLocal;
    if (!FileTimeToSystemTime(&ftLocal, &stLocal))
		return false;
    WCHAR buf[64];
	LCID loc = GetThreadLocale();
	if (date) { // convert timestamp to a date string
		if (!GetDateFormatW(loc, DATE_SHORTDATE, &stLocal, 0, buf, sizeof(buf)/sizeof(buf[0])))
			return false;
		strDateTime = buf;
		if (time)
			strDateTime += L' ';
	}
	if (time) { // convert timestamp to a time string
		if (!GetTimeFormatW(loc, 0, &stLocal, 0, buf, sizeof(buf)/sizeof(buf[0])))
			return false;
	    strDateTime += buf;
	}
    return true;
}

// privileges

bool AdjustPrivileges(const wchar_t *names[], DWORD name_count, AdjustPrivilegesError* code, DWORD* lastError)
{
	if (lastError)
		*lastError = 0;
	if (code)
		*code = ape_None;
	HANDLE hToken;
	if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) { // obtain the processes token
		if (lastError)
			*lastError = GetLastError();
		if (code)
			*code = ape_OpenProcessToken;
		return false;
	}
	TOKEN_PRIVILEGES tp;
	AdjustPrivilegesError err = ape_None;
	for (DWORD i = 0; i < name_count; ++i)
		if (LookupPrivilegeValueW(0, names[i], &tp.Privileges[0].Luid)) { // get the luid
			tp.PrivilegeCount = 1;
			tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
			if (!AdjustTokenPrivileges(hToken, FALSE, &tp, 0, (PTOKEN_PRIVILEGES)NULL, 0))
				err = ape_AdjustTokenPrivileges;
		}
		else {
			err = ape_LookupPrivilegeValue;
			break;
		}
	if (lastError)
		*lastError = GetLastError();
	CloseHandle(hToken);
	if (code)
		*code = err;
	return err == ape_None;
}

// *** Con ***

Con::Con()
{
	Reinit(false);
}

void Con::Reinit(bool writePrefix)
{
	m_hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
	m_hStdIn = GetStdHandle(STD_INPUT_HANDLE);
	DWORD mode;
	m_redirOut = OutOK() && GetConsoleMode(m_hStdOut, &mode) == FALSE;
	m_redirIn = InOK() && GetConsoleMode(m_hStdIn, &mode) == FALSE;
	if (writePrefix && m_redirOut) {
		DWORD written;
		WriteConsoleW(m_hStdOut, "\x0EF\x0BB\x0BF", 3, &written, 0);
	}
}

bool Con::Print(const wchar_t *buf, DWORD len)
{
	DWORD written;
	if (m_redirOut) {
		wstr_t w = ReplaceStr(wstr_t(buf, len), L"\n", L"\r\n");
		str_t s;
		W2Utf8(s, w);
		assert(s.size() < ULONG_MAX);
		return WriteFile(m_hStdOut, s.data(), (ULONG)s.size(), &written, 0) != FALSE &&
			written == s.size();
	}
	return WriteConsoleW(m_hStdOut, buf, len, &written, 0) != FALSE && written == len;
}

bool Con::Print(const char *buf, DWORD len)
{
	DWORD written;
	if (m_redirOut) {
		str_t s = ReplaceStr(str_t(buf, len), "\n", "\r\n");
		assert(s.size() < ULONG_MAX);
		return WriteFile(m_hStdOut, s.data(), (ULONG)s.size(), &written, 0) != FALSE && written == s.size();
	}
	return WriteConsoleA(m_hStdOut, buf, len, &written, 0) != FALSE && written == len;
}

bool Con::Printf(const wchar_t *fmt, ...)
{
	wstr_t w;
	va_list va;
	va_start(va, fmt);
	bool ok = StlFunc::StrVPrintf(w, fmt, va);
	va_end(va);
	return ok && Print(w);
}

bool Con::Printf(const char *fmt, ...)
{
	str_t s;
	va_list va;
	va_start(va, fmt);
	bool ok = StlFunc::StrVPrintf(s, fmt, va);
	va_end(va);
	return ok && Print(s);
}

}
